package com.yunchang.spring.visitor.core.service;

import com.yunchang.spring.visitor.core.dao.IDao;
import com.yunchang.spring.visitor.core.exception.DataNotFoundException;
import com.yunchang.spring.visitor.core.utils.TypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * <pre>
 * 对数据进行增删改查服务的基础实现类。
 * 实现的子类,需要注意如下事项:
 * 1.@ServiceName注解命名别名，用于Controller请求的资源唯一标识。
 * 2.@ServiceMethod注解用于指定接口访问的黑白名单(如果不加注解，表示允许所有接口访问)。
 * 3.@ServiceParam注解用于过滤接口的入参和返回值(如果不加注解，表示允许所有参数字段)。
 * 4.子类可以自定处理入参和返回(在@ServiceParam注解过滤内部)，
 * 比如替换参数的某个字段，字符集处理等，需要覆盖如下方法的实现：
 *      handleInputParam()
 *      handleOutputParam()
 * 4.该类的操作是针对单主键数据，如果数据对象是组合键，需要覆盖如下方法的实现:
 *      getInstance()
 *      instance2Map()
 * 5.add操作，可添加前置、后置处理控制，需要覆盖如下方法的实现:
 *      preAdd()
 *      postAdd()
 * 6.update操作，可添加前置、后置处理控制，需要覆盖如下方法的实现:
 *      preUpdate()
 *      postUpdate()
 * 7.remove操作，可添加前置、后置处理控制，需要覆盖如下方法的实现:
 *      preRemove()
 *      postRemove()
 * 6.add和update操作中的日期字段，可以是13位纯数字时间戳，也可以是"yyyy-MM-dd"日期格式，会自动转为日期值。
 * 7.list操作的返回如果需要排序，需要覆盖如下方法的实现:
 *      getComparator()
 * </pre>
 * Created by jasontujun on 2017/3/22.
 */
public abstract class ARestfulService<T> implements IRestfulService {

    private static final Logger logger = LoggerFactory.getLogger(ARestfulService.class);

    protected abstract IDao<T> getDao();

    /**
     * 子类需要对输入的参数集合做自定义处理，可以覆盖此方法。<br/>
     * 比如：对数据的某个字段做参数替换。<br/>
     * 注意：此方法在@ServiceParam(input)注解处理后，才被调用
     *
     * @param param 输入参数集合
     * @return 返回自定义处理后的输入参数集合
     */
    protected Map<String, String> handleInputParam(Map<String, String> param) throws Exception {
        return param;
    }

    /**
     * 子类需要对返回的参数集合做自定义处理，可以覆盖此方法。<br/>
     * 比如：对数据的某个字段做字符集转换等。<br/>
     * 注意：此方法在@ServiceParam(output)注解处理后，才被调用
     *
     * @param param 返回参数集合
     * @return 返回自定义处理后的返回参数集合
     */
    protected Map handleOutputParam(Map param) throws Exception {
        return param;
    }

    /**
     * 更新对象字段的值
     *
     * @param instance   对象
     * @param properties 参数集合
     * @return 返回原来的对象
     */
    protected final T mergeInstance(T instance, Map<String, String> properties) {
        if (instance == null) {
            return null;
        }
        if (properties == null || properties.isEmpty()) {
            return instance;
        }
        // 遍历对象的属性，更新对应字段的值
        Field[] fields = getDao().getEntityClass().getDeclaredFields();
        for (Field field : fields) {
            // 只更新对象存在的字段
            if (!properties.containsKey(field.getName())) {
                continue;
            }
            //对参数中的值进行类型适配处理
            String value = properties.get(field.getName());
            // 将String类型的参数值转为对应类型的值
            Object realValue = TypeUtil.parseValue(value, field.getType());
            //设置属性值
            setFieldValue(instance, field, realValue);
        }
        return instance;
    }

    /**
     * 设置instance的field参数值为realValue 先查找field对应的set方法，若无则直接设置field的值
     * (PropertyDescriptor 该属性必须同时有public get/set方法，否则抛异常, 执行field的set)
     *
     * @param instance  对象
     * @param field     字段类型
     * @param realValue 字段值
     */
    private void setFieldValue(T instance, Field field, Object realValue) {
        Method setMethod = null;
        try {
            PropertyDescriptor pd = new PropertyDescriptor(field.getName(),
                    instance.getClass());
            setMethod = pd.getWriteMethod();
        } catch (IntrospectionException e) {
            logger.error("", e);
        }
        try {
            if (setMethod != null) {
                // 通过setter方法更新字段的值
                setMethod.setAccessible(true);
                setMethod.invoke(instance, realValue);
            } else {
                // 通过field直接更新字段的值
                field.setAccessible(true);
                field.set(instance, realValue);
            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            logger.error("", e);
        }
    }

    /**
     * 将字段集合转化成对象
     *
     * @param properties 参数集合
     * @return 返回新的对象
     */
    protected T map2Instance(Map<String, String> properties) throws Exception {
        if (properties == null || properties.isEmpty()) {
            return null;
        }
        try {
            T instance = getDao().getEntityClass().newInstance();
            return mergeInstance(instance, properties);
        } catch (InstantiationException | IllegalAccessException e) {
            logger.error("", e);
        }
        return null;
    }

    /**
     * 将对象转换为字段集合
     *
     * @param instance 对象
     * @return 返回参数集合
     */
    protected Map instance2Map(T instance) {
        if (instance == null) {
            return new HashMap();
        }
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(instance.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                if (key.compareToIgnoreCase("class") == 0) {
                    continue;
                }
                //实体属性的类型必须是基本类型  或者 是可序列化的
                Class type = property.getPropertyType();
                if (!type.isPrimitive() && !Serializable.class.isAssignableFrom(type)) {
                    continue;
                }
                Method getter = property.getReadMethod();
                Object value = getter != null ? getter.invoke(instance) : null;
                map.put(key, value);
            }
        } catch (Exception e) {
            logger.error("", e);
        }
        return map;
    }

    /**
     * 通过id获取到对象。默认是db.get()。(子类可以覆盖此方法来自定义单个查询)
     */
    protected T getInstance(String id, Map<String, String> properties) {
        return getDao().get(id);
    }

    /**
     * 对list方法的入参进行预处理
     * @param properties 入参
     */
    protected void preList(Map<String, String> properties) {
    }

    /**
     * 对list分页查询的结果进行处理
     * @param startIndex 起始索引
     * @param pageSize 页大小
     * @param inputParams 查询入参
     * @param result 查询结果
     * @return 返回的数据
     * @throws Exception
     */
    protected Object postPageList(int startIndex, int pageSize,
                                  Map<String, String> inputParams,
                                  List<T> result) throws Exception{
        List<Map> results = new ArrayList<Map>();
        for (T instance : result) {
            results.add(handleOutputParam(instance2Map(instance)));
        }
        return results;
    }

    /**
     * 新增前的自定义逻辑(子类可以覆盖此方法来控制新增)。
     * map2Instance()之后，dao.save()之前。
     *
     * @param instance   对象
     * @param properties 传入参数
     */
    protected void preAdd(T instance, Map<String, String> properties) {
    }

    /**
     * 新增进数据库后的自定义逻辑(子类可以覆盖此方法来进行后续操作)。
     * dao.save()之后。
     *
     * @param instance   对象
     * @param properties 传入参数
     */
    protected void postAdd(T instance, Map<String, String> properties) {
    }

    /**
     * 更新前的自定义逻辑(子类可以覆盖此方法来控制更新)。
     * mergeInstance()和db.save()之前。
     *
     * @param instance   对象
     * @param properties 传入参数
     * @return 返回你已经处理过的参数key列表，这些key在后续mergeInstance()中会被忽略。
     * 如果返回为null或空，则properties中所有参数都会进行mergeInstance()
     */
    protected List<String> preUpdate(T instance, Map<String, String> properties) {
        return null;
    }

    /**
     * 更新进数据库后的自定义逻辑(子类可以覆盖此方法来进行后续操作)
     *
     * @param instance   对象
     * @param properties 传入参数
     */
    protected void postUpdate(T instance, Map<String, String> properties) {
    }

    /**
     * 删除该对象前的自定义逻辑(子类可以覆盖此方法来控制删除)
     *
     * @param instance 对象
     */
    protected void preRemove(T instance) {
    }

    /**
     * 删除该对象后的自定义逻辑(子类可以覆盖此方法来进行后续操作)
     *
     * @param instance 对象
     */
    protected void postRemove(T instance) {
    }

    /**
     * 对结果集合的排序。如果返回为空，则不对结果集进行排序
     */
    protected Comparator<T> getComparator() {
        return null;
    }

    /**
     * 分页查询的参数key列表。
     * 如果要支持分页查询，子类必须覆盖该方法。
     * @return 返回[分页起始索引，页大小，排序字段]；返回为空，则list方法不进行分页
     */
    protected String[] getPageKeys() {
        return null;
    }

    /**
     * 分页的结果，是否是升序。
     * @param params 入参
     * @return 默认返回true，升序
     */
    protected boolean isPageASC(Map<String, String> params) {
        return true;
    }

    /**
     * 判断字符是否是纯数字
     */
    private static boolean isNumber(String str) {
        if (StringUtils.isEmpty(str)) {
            return false;
        }
        for (int i = 0; i < str.length(); i++) {
            if (!Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public Object list(Map<String, String> properties) throws Exception {
        Map<String, String> params = new HashMap<String, String>(properties);
        preList(params);
        String[] pageKeys = getPageKeys();
        if (pageKeys == null || pageKeys.length < 3) {
            // 非分页查询
            return listAll(params);
        }
        else {
            // 如果没有起始索引或页大小，则执行非分页查询
            if (!params.containsKey(pageKeys[0]) || !params.containsKey(pageKeys[1])) {
                return listAll(params);
            }
            // 执行分页查询
            int startIndex = -1;
            if (params.containsKey(pageKeys[0])) {
                String startStr = params.remove(pageKeys[0]);
                startIndex = isNumber(startStr) ? Integer.valueOf(startStr) : -1;
            }
            int pageSize = -1;
            if (params.containsKey(pageKeys[1])) {
                String sizeStr = params.remove(pageKeys[1]);
                pageSize = isNumber(sizeStr) ? Integer.valueOf(sizeStr) : -1;
            }
            String sortKey = "";
            if (params.containsKey(pageKeys[2])) {
                sortKey = params.remove(pageKeys[2]);
            }
            return listByPage(startIndex, pageSize, sortKey, params);
        }
    }

    // 返回所有查询结果
    private Object listAll(Map<String, String> params) throws Exception {
        List<T> instances = getDao().findAll(params);
        if (instances == null) {
            return new ArrayList<Map>();
        }
        Comparator<T> comparator = getComparator();
        if (comparator != null) {
            Collections.sort(instances, comparator);
        }
        List<Map> results = new ArrayList<Map>();
        for (T instance : instances) {
            results.add(handleOutputParam(instance2Map(instance)));
        }
        return results;
    }

    // 分页返回查询结果
    private Object listByPage(int startIndex, int pageSize, String sortKey,
                              Map<String, String> params) throws Exception {
        boolean isASC = isPageASC(params);
        List<T> instances = getDao().findAllByPage(startIndex, pageSize, sortKey, isASC, params);
        return postPageList(startIndex, pageSize, params, instances);
    }

    @Override
    public Map get(String id, Map<String, String> params) throws Exception {
        T instance = getInstance(id, params);
        if (instance == null) {
            throw new DataNotFoundException("Sorry, Data not found");
        }
        return handleOutputParam(instance2Map(instance));
    }

    @Override
    public Map add(Map<String, String> properties) throws Exception {
        properties = handleInputParam(new HashMap<String, String>(properties));
        T instance = map2Instance(new HashMap<String, String>(properties));
        if (instance == null) {
            throw new DataNotFoundException("Sorry, Data not found");
        }
        preAdd(instance, new HashMap<String, String>(properties));
        getDao().save(instance);
        postAdd(instance, new HashMap<String, String>(properties));
        return handleOutputParam(instance2Map(instance));
    }

    @Override
    public Map update(String id, Map<String, String> properties) throws Exception {
        properties = handleInputParam(new HashMap<String, String>(properties));
        T instance = getInstance(id, new HashMap<String, String>(properties));
        if (instance == null) {
            throw new DataNotFoundException("Sorry, Data not found");
        }
        List<String> ignoreKeys = preUpdate(instance, new HashMap<String, String>(properties));
        // 主键字段不可以更新，过滤掉
        Map<String, String> wrapperParams = (properties == null ? null : new HashMap<String, String>(properties));
        if (wrapperParams != null) {
            wrapperParams.remove(getDao().getIdFieldName());
            if (ignoreKeys != null) {
                for (String key : ignoreKeys) {
                    wrapperParams.remove(key);
                }
            }
        }
        instance = mergeInstance(instance, wrapperParams);
        if (instance == null) {
            return null;
        }
        getDao().update(instance);
        postUpdate(instance, new HashMap<String, String>(properties));
        return handleOutputParam(instance2Map(instance));
    }

    private Boolean isOverrideGetInstance = null;

    @Override
    public Map remove(String id, Map<String, String> params) throws Exception {
        T instance = null;
        //判断子类是否重写getInstance方法，如果重写则使用子类的getInstance方法，如果没有则使用dao.load()
        if (isOverrideGetInstance == null) {
            isOverrideGetInstance = TypeUtil.isOverride(this.getClass(), ARestfulService.class, "getInstance", String.class, Map.class);
        }
        if (isOverrideGetInstance) {
            instance = getInstance(id, params);
        } else {
            instance = getDao().load(id);
        }
        if (instance == null) {
            throw new DataNotFoundException("Sorry, Data not found");
        }
        preRemove(instance);
        getDao().remove(instance);
        postRemove(instance);
        return handleOutputParam(instance2Map(instance));
    }

}
